####################################
### SSA/ASS structures & classes ###
####################################

# http://tcax.rhacg.com/docs/ass-specs.htm
# http://docs.aegisub.org/manual/ASS_Tags
# http://matroska.org/technical/specs/subtitles/ssa.html
# http://wiki.multimedia.cx/index.php?title=SubStation_Alpha

# FIXME: colour converting functions for ssa <=> ass are still incomplete

def colour_to_colour_int(colour):
	if (warnings):
		print("WARNING: colour format (" + colour + ") cannot be converted to SSA integer!")
	colour_int = "16777215" # FFFFFF
	return colour_int

def colour_to_colour_hex(colour):
	if (colour[0:2] == "&H"):
		if (len(colour) == 10):
			return colour
		elif (len(colour) == 8):
			return "&H00" + colour[2:]
		else:
			if (warnings):
				print("WARNING: colour format (" + colour + ") cannot be converted to ASS &HAABBGGRR!")
	colour_hex = "&H00FFFFFF" # 16777215
	return colour_hex

class ssa_header:

	comments = []						# comments
	title = "Untitled"					# script title
	script_type = "V4.00+"				# script version
	wrap_style = 0 						# [>= V4.00+] 0 = wrap lines equally
	play_res_x = 0 						# script virtual width mapped on the width of the used video -> subtitle works for videos with different resolutions
	play_res_y = 0 						# script virtual height mapped on the width of the used video -> subtitle works for videos with different resolutions
	play_depth = 0 						# playback depth
	scaled_border_and_shadow = "no"		# 
	collisions = "Normal" 				# Normal or Reverse
	synch_point = 0 					# [opt] start time for playback of this script
	timer = 100.0000 					# [opt] multiplier for timestamps (percent)
	private_header_lines = [] 			# [>= V4.00+] private options mostly used in subtitle creation software

	def get_parameters(self, advanced):
		line = ""
		# NOTE: do not export comments
		#for comment in self.comments:
		#	line += comment.replace("!:", ";") + newline
		line += "Title: " + self.title + newline
		if (advanced):
			line += "ScriptType: V4.00+" + newline
		else:
			line += "ScriptType: V4.00" + newline
		if (advanced):
			line += "WrapStyle: " + str(self.wrap_style) + newline
		if (self.play_res_x > 0):
			line += "PlayResX: " + str(self.play_res_x) + newline
		if (self.play_res_y > 0):
			line += "PlayResY: " + str(self.play_res_y) + newline
		if (self.play_depth > 0):
			line += "PlayDepth: " + str(self.play_depth) + newline
		if (self.scaled_border_and_shadow.lower() == "yes"):
			line += "ScaledBorderAndShadow: " + self.scaled_border_and_shadow + newline
		line += "Collisions: " + self.collisions + newline
		if (self.synch_point != 0):
			line += "Synch Point: " + str(self.synch_point) + newline
		if (self.timer != 100.0):
			line += "Timer: %.4f" % self.timer + newline
		if (len(self.private_header_lines) > 0):
			if (warnings):
				print("WARNING: private header fields will not be exported!")
		# NOTE: do not export private fields
		#if (advanced):
		#	for addline in self.private_header_lines:
		#		line += addline + newline
		return line

	def set_parameter(self, key, value):
		if (key.startswith(";") or key.startswith("!:")):
			# check if comment was splitted as key/value pair because it contained ':'
			if (value):
				self.comments.append(key.replace("!:", ";") + ":" + value)
			else:
				self.comments.append(key.replace("!:", ";"))
		elif (key == "Title"):
			self.title = value
		elif (key == "ScriptType"):
			self.script_type = value
		elif (key == "WrapStyle"):
			self.wrap_style = int(value)
		elif (key == "PlayResX"):
			self.play_res_x = int(value)
		elif (key == "PlayResY"):
			self.play_res_y = int(value)
		elif (key == "PlayDepth"):
			self.play_depth = int(value)
		elif (key == "ScaledBorderAndShadow"):
			self.scaled_border_and_shadow = value
		elif (key == "Collisions"):
			self.collisions = value
		elif (key == "Synch Point"):
			self.synch_point = int(value)
		elif (key == "Timer"):
			self.timer = float(value)
		else:
			if (key):
				# check if private header was splitted as key/value pair because it contained ':'
				if(value):
					self.private_header_lines.append(key + ": " + value)
				else:
					self.private_header_lines.append(key)

class ssa_style:

	name = "Default"					# case sensitive no ',' allowed
	font_face = ""						#
	font_size = 24						#
	primay_colour = "&H00FFFFFF"		# AABBGGRR colour of text1
	secondary_colour = "&H00FFFFFF"		# AABBGGRR colour of text2
	outline_colour = "&H00000000"		# AABBGGRR colour of text3 (SSA) | textoutline (ASS)
	back_colour = "&H80000000"			# AABBGGRR colour of text shadow ?
	bold = 0							# -1 = true, 0 = false
	italic = 0							# -1 = true, 0 = false
	underline = 0						# -1 = true, 0 = false
	strikeout = 0						# -1 = true, 0 = false
	scale_x = 100						# percent
	scale_y = 100						# percent
	spacing = 0							# pixel
	angle = 0.0							# text rotation in degrees
	border_style = 1					# 1 = outline + shadow
	outline = 1.5						# outline size (pixel)
	shadow = 0.0						# shadow size (pixel)
	alignment = 2						# left, right center ... top, middle, bottom -> numpad layout (2 = center bottom)
	margin_left = 24					# pixel
	margin_right = 24					# pixel
	margin_top_bottom = 16				# pixel
	alpha_level = 0						# text transparency (unused)
	encoding = 0						# text character encoding

	@staticmethod
	def get_format(advanced):
		line = "Format: "
		line += "Name, "
		line += "Fontname, "
		line += "Fontsize, "
		line += "PrimaryColour, "
		line += "SecondaryColour, "
		if (advanced):
			line += "OutlineColour, "
		else:
			line += "TertiaryColour, "
		line += "BackColour, "
		line += "Bold, "
		line += "Italic, "
		if (advanced):
			line += "Underline, "
			line += "StrikeOut, "
			line += "ScaleX, "
			line += "ScaleY, "
			line += "Spacing, "
			line += "Angle, "
		line += "BorderStyle, "
		line += "Outline, "
		line += "Shadow, "
		line += "Alignment, "
		line += "MarginL, "
		line += "MarginR, "
		line += "MarginV, "
		if (not(advanced)):
			line += "AlphaLevel, "
		line += "Encoding"
		return line

	def get_parameters(self, advanced):
		line = "Style: "
		line += self.name + ","
		line += self.font_face + ","
		line += str(self.font_size) + ","
		if (advanced):
			line += colour_to_colour_hex(self.primay_colour) + ","
			line += colour_to_colour_hex(self.secondary_colour) + ","
			line += colour_to_colour_hex(self.outline_colour) + ","
			line += colour_to_colour_hex(self.back_colour) + ","
		else:
			line += colour_to_colour_int(self.primay_colour) + ","
			line += colour_to_colour_int(self.secondary_colour) + ","
			line += colour_to_colour_int(self.outline_colour) + ","
			line += colour_to_colour_int(self.back_colour) + ","
		line += str(self.bold) + ","
		line += str(self.italic) + ","
		if (advanced):
			line += str(self.underline) + ","
			line += str(self.strikeout) + ","
			line += str(self.scale_x) + ","
			line += str(self.scale_y) + ","
			line += str(self.spacing) + ","
			line += ("%.1f," % self.angle).replace(".0", "")
		line += str(self.border_style) + ","
		line += ("%.1f," % self.outline).replace(".0", "")
		line += ("%.1f," % self.shadow).replace(".0", "")
		line += str(self.alignment) + ","
		line += str(self.margin_left) + ","
		line += str(self.margin_right) + ","
		line += str(self.margin_top_bottom) + ","
		if (not(advanced)):
			line += str(self.alpha_level) + ","
		line += str(self.encoding)
		return line

	def set_parameters(self, keys, values):
		for [key, value] in zip(keys, values):
			if (key == "Name"):
				self.name = value
			elif (key == "Fontname"):
				self.font_face = value
			elif (key == "Fontsize"):
				self.font_size = int(value)
			elif (key == "PrimaryColour"):
				self.primay_colour = colour_to_colour_hex(value)
			elif (key == "SecondaryColour"):
				self.secondary_colour = colour_to_colour_hex(value)
			elif ((key == "TertiaryColour") or (key == "OutlineColour")):
				self.outline_colour = colour_to_colour_hex(value)
			elif (key == "BackColour"):
				self.back_colour = colour_to_colour_hex(value)
			elif (key == "Bold"):
				self.bold = int(value)
			elif (key == "Italic"):
				self.italic = int(value)
			elif (key == "Underline"):
				self.underline = int(value)
			elif (key == "StrikeOut"):
				self.strikeout = int(value)
			elif (key == "ScaleX"):
				self.scale_x = int(value)
			elif (key == "ScaleY"):
				self.scale_y = int(value)
			elif (key == "Spacing"):
				self.spacing = int(value)
			elif (key == "Angle"):
				self.angle = float(value)
			elif (key == "BorderStyle"):
				self.border_style = int(value)
			elif (key == "Outline"):
				self.outline = float(value)
			elif (key == "Shadow"):
				self.shadow = float(value)
			elif (key == "Alignment"):
				self.alignment = int(value)
			elif (key == "MarginL"):
				self.margin_left = int(value)
			elif (key == "MarginR"):
				self.margin_right = int(value)
			elif (key == "MarginV"):
				self.margin_top_bottom = int(value)
			elif (key == "AlphaLevel"):
				self.alpha_level = int(value)
			elif (key == "Encoding"):
				self.encoding = int(value)
			else:
				if (key != "Format"):
					if (warnings):
						print("WARNING: unknown key/value in style (" + key + ": " + value + ") will be ignored!")

class ssa_event:

	event_type = "Dialogue"			# Dialogue, Comment, Picture, Sound, Movie, Command
	layer = 0						# marked in ssa [Marked=0, Marked=1] | layer in ass [any integer]
	time_start = "00:00:00.000"		# consider ssa/ass has single digit for hours, two digits for micro!
	time_end = "00:00:00.000"		# consider ssa/ass has single digit for hours, two digits for micro!
	style_template = "Default"		#
	narrator_name = ""				# name of the actor who is speaking this dialogue
	margin_left = 0					# override style template margin_left (pixel in 4-digit format)
	margin_right = 0				# override style template margin_right (pixel in 4-digit format)
	margin_top_bottom = 0			# override style template margin_top_bottom (pixel in 4-digit format)
	effect = ""						#
	content = ""					# Dialogue: display text may contain directives {\*} and linebreaks \n \N and commas
									# Comment: ignored text may contain directives {\*} and linebreaks \n \N and commas
									# Picture: path to picture that is shown
									# Sound: path to wav file that should be played
									# Movie: path to avi file that should be played
									# Command: path to application or internal command that should be executed

	@staticmethod
	def get_format(advanced):
		line = "Format: "
		if (advanced):
			line += "Layer, "
		else:
			line += "Marked, "
		line += "Start, "
		line += "End, "
		line += "Style, "
		line += "Name, "
		line += "MarginL, "
		line += "MarginR, "
		line += "MarginV, "
		line += "Effect, "
		line += "Text"
		return line
	
	def get_parameters(self, advanced):
		line = self.event_type + ": "
		if (advanced):
			line += str(self.layer) + ","
		else:
			line += "Marked=0,"
		line += self.time_start[1:-1] + ","
		line += self.time_end[1:-1] + ","
		line += self.style_template + ","
		line += self.narrator_name + ","
		line += str(self.margin_left).zfill(4) + ","
		line += str(self.margin_right).zfill(4)  + ","
		line += str(self.margin_top_bottom).zfill(4)  + ","
		if (advanced):
			line += self.effect + ","
		else:
			line += ","
		if (advanced):
			line += self.content
		else:
			# FIXME: remove all unsupported override tags from override blocks i.e. underline, strikeout, edgeblur, ...
			line += self.content
		return line

	def set_parameters(self, keys, values):
		for [key, value] in zip(keys, values):
			if (key == "Format"):
				self.event_type = value;
			elif (key == "Layer"):
				self.layer = int(value);
			elif (key == "Marked"):
				self.layer = 0
			elif (key == "Start"):
				self.time_start = value.zfill(11) + "0";
			elif (key == "End"):
				self.time_end = value.zfill(11) + "0";
			elif (key == "Style"):
				self.style_template = value;
			elif (key == "Name"):
				self.narrator_name = value;
			elif (key == "MarginL"):
				self.margin_left = int(value);
			elif (key == "MarginR"):
				self.margin_right = int(value);
			elif (key == "MarginV"):
				self.margin_top_bottom = int(value);
			elif (key == "Effect"):
				self.effect = value;
			elif (key == "Text"):
				self.content = value;
			else:
				if (warnings):
					print("WARNING: unknown key/value in event (" + key + ": " + value + ") will be ignored!")

class ssa_font:

	name = ""
	data = [] # each line is 80 characters long, except the last line

	def get_parameters(self, advanced):
		line = "fontname: " + self.name
		for raw in data:
			line += newline + raw
		return line

class ssa_graphic:

	name = ""
	data = [] # each line is 80 characters long, except the last line

	def get_parameters(self, advanced):
		line = "filename: " + self.name
		for raw in data:
			line += newline + raw
		return line

class ssa:

	header = ssa_header()
	styles = []
	events = []
	fonts = []
	graphics = []

	def load(self, filename):
		print("Loading: '" + filename + "'")
		filestream = open(filename, 'r')
		line = strip_line(filestream.next())
		self.load_section(line, filestream)
		filestream.close()

	def load_section(self, section_type, filestream):
		section_type = strip_line(section_type)
		if (section_type.upper() == "[SCRIPT INFO]"):
			self.load_header(filestream)
		if (section_type.upper() == "[V4+ STYLES]"):
			self.load_styles(filestream)
		if (section_type.upper() == "[EVENTS]"):
			self.load_events(filestream)
		if (section_type.upper() == "[FONTS]"):
			self.load_fonts(filestream)
		if (section_type.upper() == "[GRAPHICS]"):
			self.load_graphics(filestream)

	def load_header(self, filestream):
		key_value = []
		while (1):
			# read next line / return on EOF
			try:
				line = strip_line(filestream.next())
			except StopIteration:
				return
			# check if the line belongs to the header section (no new section: [*])
			if (not(re.match(r"\[.*\]", line))):
				key_value = map(str.strip, line.partition(":"))
				self.header.set_parameter(key_value[0], key_value[2])
			else:
				return self.load_section(line, filestream)

	def load_styles(self, filestream):
		style_key_count = 0
		style_keys = []
		style_values = []
		style_types = ["Style"]
		while (1):
			# read next line / return on EOF
			try:
				line = strip_line(filestream.next())
			except StopIteration:
				return
			# check if the line belongs to the style section (no new section: [*])
			if (not(re.match(r"\[.*\]", line))):
				# only process line when we already knows the keys
				if (style_key_count > 0):
					style_values = map(str.strip, line.replace(":", ",", 1).split(",", style_key_count-1))
					if (style_types.count(style_values[0]) > 0):
						style = ssa_style()
						style.set_parameters(style_keys, style_values)
						self.styles.append(style)
					else:
						if (style_values[0] and warnings):
							print("WARNING: unknown style entry (" + style_values[0] + ") will be ignored!")
				else:
					if (line.startswith("Format:")):
						style_keys = map(str.strip, line.replace(":", ",", 1).split(","))
						style_key_count = len(style_keys)
					else:
						if (line and warnings):
							print("WARNING: style entry without key definition will be ignored!")
			else:
				return self.load_section(line, filestream)

	def load_events(self, filestream):
		event_key_count = 0
		event_keys = []
		event_values = []
		event_types = ["Dialogue", "Comment", "Picture", "Sound", "Movie", "Command"]
		while (1):
			# read next line / return on EOF
			try:
				line = strip_line(filestream.next())
			except StopIteration:
				return
			# check if the line belongs to the event section (no new section: [*])
			if (not(re.match(r"\[.*\]", line))):
				# only process line when we already knows the keys
				if (event_key_count > 0):
					event_values = map(str.strip, line.replace(":", ",", 1).split(",", event_key_count-1))
					if (event_types.count(event_values[0]) > 0):
						event = ssa_event()
						event.set_parameters(event_keys, event_values)
						self.events.append(event)
					else:
						if (event_values[0] and warnings):
							print("WARNING: unknown event entry (" + event_values[0] + ") will be ignored!")
				else:
					if (line.startswith("Format:")):
						event_keys = map(str.strip, line.replace(":", ",", 1).split(","))
						event_key_count = len(event_keys)
					else:
						if (line and warnings):
							print("WARNING: event entry without key definition will be ignored!")
			else:
				return self.load_section(line, filestream)

	def load_fonts(self, filestream):
		while (1):
			# read next line / return on EOF
			try:
				# FIXME: strip_line() might remove data when it is same as UTF-BOM key
				line = strip_line(filestream.next())
			except StopIteration:
				return
			# check if the line belongs to the font section (no new section: [*])
			if (not(re.match(r"\[.*\]", line))):
				if (line.startswith("fontname:")):
					font = ssa_font()
					font.name = line.partition(":")[2].strip()
					font.data = []
					self.fonts.append(font)
				elif (line):
					self.fonts[len(self.fonts)-1].data.append(line)
			else:
				return self.load_section(line, filestream)

	def load_graphics(self, filestream):
		while (1):
			# read next line / return on EOF
			try:
				# FIXME: strip_line() might remove data when it is same as UTF-BOM key
				line = strip_line(filestream.next())
			except StopIteration:
				return
			# check if the line belongs to the font section (no new section: [*])
			if (not(re.match(r"\[.*\]", line))):
				if (line.startswith("filename:")):
					graphic = ssa_graphic()
					graphic.name = line.partition(":")[2].strip()
					graphic.data = []
					self.graphics.append(graphic)
				elif (line):
					self.graphics[len(self.graphics)-1].data.append(line)
			else:
				return self.load_section(line, filestream)

	def save(self, filename):
		print("Saving: '" + filename + "'")
		advanced = (".ass" == os.path.splitext(filename)[1].strip().lower())
		filestream = open(filename, 'w')

		# write header
		filestream.write("[Script Info]" + newline)
		filestream.write("; Creation Time: " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + newline)
		filestream.write("; Created With: SubConv " + version + newline)
		filestream.write("; http://subconv.googlecode.com" + newline)
		filestream.write(self.header.get_parameters(advanced))

		# write styles
		filestream.write(newline)
		if (advanced):
			filestream.write("[V4+ Styles]" + newline)
		else:
			filestream.write("[V4 Styles]" + newline)
		filestream.write(ssa_style.get_format(advanced) + newline)
		if (len(self.styles) == 0):
			filestream.write(ssa_style().get_parameters(advanced) + newline)
			if (warnings):
				print("WARNING: no styles found, default style has been added!")
		for style in self.styles:
			filestream.write(style.get_parameters(advanced) + newline)

		# write events
		if (len(self.events) > 0):
			filestream.write(newline)
			filestream.write("[Events]" + newline)
			filestream.write(ssa_event.get_format(advanced) + newline)
			for event in self.events:
				filestream.write(event.get_parameters(advanced) + newline)
		else:
			if (warnings):
				print("WARNING: there are no events (subtitles) for export available!")

		# write fonts
		if (len(self.fonts) > 0):
			filestream.write(newline)
			filestream.write("[Fonts]" + newline)
			for font in self.fonts:
				filestream.write("fontname: " + font.name + newline)
				for dataline in font.data:
					filestream.write(dataline + newline)

		# write graphics
		if (len(self.graphics) > 0):
			filestream.write(newline)
			filestream.write("[Graphics]" + newline)
			for graphic in self.graphics:
				filestream.write("filename: " + graphic.name + newline)
				for dataline in graphic.data:
					filestream.write(dataline + newline)

		filestream.close()
